package org.zhc.service.impl;

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.zhc.constant.Constants;
import org.zhc.context.ActionContext;
import org.zhc.exception.NoActionDefinedException;
import org.zhc.exception.NoLocationMappingException;
import org.zhc.exception.NoMatchingTypeException;
import org.zhc.exception.NoMethodDefinedException;
import org.zhc.exception.NoResultMappingException;
import org.zhc.po.Action;
import org.zhc.po.HttpContext;
import org.zhc.po.Property;
import org.zhc.po.Result;
import org.zhc.service.ActionInvokeHandler;
import org.zhc.util.TypeConvertUtil;

public class ActionInvokeHandlerImpl implements ActionInvokeHandler {

	private static final Logger logger = Logger
			.getLogger(LoadAnnotationConfig.class.getName());

	private HttpContext httpContext;
	private Object actionInc = null;
	private Map<String, String> parameters = new HashMap<String, String>();
	private Set<String> fieldNames = new HashSet<String>();

	public ActionInvokeHandlerImpl(HttpContext httpContext) {
		this.httpContext = httpContext;
	}

	public ActionInvokeHandlerImpl(HttpContext httpContext, String namespace,
			String actionname) throws NoActionDefinedException,
			ServletException, IOException {
		this.httpContext = httpContext;
	}

	/**
	 * @param namespace
	 *            命名空间
	 * @param actionname
	 *            请求的action的名称
	 * @return Result
	 *         如果在命名空间下面存在对应的action，那么返回action请求后的Result，否则抛出NoActionDefinedException
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	public void invoke(String namespace, String actionname)
			throws NoActionDefinedException, ServletException, IOException {
		synchronized (this.httpContext) {
			Action action = ActionContext.findAction(namespace, actionname);
			this.getParameters();
			Object result = null;
			try {
				Class<?> clazz = Class.forName(action.getClazz());
				actionInc = clazz.newInstance();
				synchronized (actionInc) {
					this.getFields(action);
					this.setFields();
					ActionContext.addContext(actionInc, httpContext);
					Method mth = getMethod(clazz, action.getMethod());
					result = mth.invoke(actionInc);
					Result res = null;
					if (result != null) {
						String sResult = String.valueOf(result);
						res = this.getResult(sResult, action);
					}
					this.handlerResult(res);
				}
			} catch (Exception e) {
				logger.error(e.getMessage());
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 从一个类中通过方法名称返回java.reflect.Method
	 * 
	 * @param Class
	 *            <?> clazz 请求的action的类的class
	 * @param String
	 *            methodName 需要查询的方法名称
	 * @author java.reflect.Method
	 *         返回找到的方法，如果找不到，则会抛出exception.NoMethodDefinedException
	 * @throws org.zhc.exception.NoMethodDefinedException
	 */
	@Override
	public Method getMethod(Class<?> clazz, String methodName)
			throws NoMethodDefinedException {
		if (clazz != null) {
			try {
				return clazz.getMethod(methodName);
			} catch (Exception e) {
				logger.error("There is no method " + methodName
						+ "() defined in class " + clazz.getName());
				throw new NoMethodDefinedException("There is no method "
						+ methodName + "() defined in class " + clazz.getName());
			}
		}
		logger.error("There is no method " + methodName
				+ "() defined in class " + clazz.getName());
		throw new NoMethodDefinedException("There is no method " + methodName
				+ "() defined in class " + clazz.getName());
	}

	/**
	 * 根据返回的结果值和对应的PO获得result的PO
	 * 
	 * @param value
	 *            result的值，例如：success error input等
	 * @param action
	 *            请求的Action对应的PO
	 * @return
	 */
	@Override
	public Result getResult(String value, Action action)
			throws NoResultMappingException, NoLocationMappingException {
		List<Result> results = action.getResults();
		for (Result result : results) {
			if (result.getValue() == null) {// 如果没有定义value的值
				List<Property> properties = result.getProperties();
				for (Property property : properties) {
					if (property.getName() != null
							&& property.getName().equals("location")) {
						Result _result = new Result(value, result.getType(),
								property.getContent());// 暂时这样，需要修改，当为重定向并带有参数的时候路径应修改
						_result.getAttributes().addAll(action.getAttributes());// 将action下面所有的attribute全部复制到对应result下面，方便action处理完后的处理
						return _result;
					} else {
						logger.error("There is no result \"" + value
								+ "\" mapped for " + action.getName());
						throw new NoResultMappingException(
								"There is no result \"" + value
										+ "\" mapped for " + action.getName());
					}
				}
			} else {// 定义了value的值
				if (result.getValue().equals(value)) {
					if (result.getLocation() == null
							|| result.getLocation().trim().equals("")) {// 如果result找不到location，那么就到properties里面去找
						String location = this.findLocation(result.getValue(),
								result.getProperties());
						if (location == null) {// 如果property里面找不到，将抛出异常
							logger.error("There is no location mapping for result \""
									+ result.getValue() + "\".");
							throw new NoLocationMappingException(
									"There is no location mapping for result \""
											+ result.getValue() + "\".");
						}
						Result _result = new Result(result.getValue(),
								result.getType(), location,
								result.getProperties(), result.getAttributes());
						_result.getAttributes().addAll(action.getAttributes());
						return _result;
					}
					return result;
				}
			}
		}
		logger.error("There is no result \"" + value + "\" mapped.");
		throw new NoResultMappingException("There is no result \"" + value
				+ "\" mapped.");
	}

	/**
	 * 用来查找po.Result中的properties中的location
	 * 
	 * @pramati value action执行完毕后返回的result的值
	 * @param properties
	 * @return
	 */
	private String findLocation(String value, List<Property> properties) {
		if (properties == null)
			return null;
		for (Property property : properties) {
			if (property.getName().equals("location")) {
				return property.getContent();
			}
			logger.error("There is no location mapped for result \"" + value
					+ "\"");
			throw new NoLocationMappingException(
					"There is no location mapped for result \"" + value + "\"");
		}
		logger.error("There is no location mapped for result \"" + value + "\"");
		throw new NoLocationMappingException(
				"There is no location mapped for result \"" + value + "\"");
	}

	@Override
	public void handlerResult(Result result) throws ServletException,
			IOException {
		try {
			if (result != null) {
				if (result.getType().equals(Constants.FORWARD)) {
					this.forward(result);
				} else if (result.getType().equals(Constants.INCLUED)) {
					this.include(result);
				} else if (result.getType().equals(Constants.REDIRECT)) {
					this.redirect(result);
				} else if (result.getType().equals(Constants.JSON)) {
					this.jsonWrite(result);
				} else if (result.getType().equals(Constants.GENERAL)) {
					this.generalWrite(result);
				} else {
					throw new NoMatchingTypeException("No matching type \""
							+ result.getType() + "\"!");
				}
			}
		} finally {// 无论有没有返回结果，都应该从ActionContext里面将用完的HttpContext移除掉，否则会造成内存溢出
			this.destoryHttpContext();
		}
	}

	/**
	 * 进行forward用到的方法
	 * 
	 * @param result
	 *            进行跳转中包含的所有信息
	 */
	@Override
	public void forward(Result result) throws ServletException, IOException {
		this.setAttributes(result);
		this.httpContext
				.getRequest()
				.getRequestDispatcher(result.getLocation())
				.forward(this.httpContext.getRequest(),
						this.httpContext.getResponse());
	}

	/**
	 * 做include转发的时候处理方法
	 * 
	 * @param result
	 *            转发的Result的实例
	 * 
	 */
	@Override
	public void include(Result result) throws ServletException, IOException {
		this.setAttributes(result);
		this.httpContext
				.getRequest()
				.getRequestDispatcher(result.getLocation())
				.include(this.httpContext.getRequest(),
						this.httpContext.getResponse());
	}

	/**
	 * 重定向用到的方法
	 * 
	 * @param result
	 *            进行跳转中包含的所有信息
	 */
	@Override
	public void redirect(Result result) throws IOException {
		String location = this.rebuildLocation(result);
		this.httpContext.getResponse().sendRedirect(
				ActionContext.getContextPath() + location);
	}

	/**
	 * 当前用户配置的类型为JSON时，应由以下方法处理
	 * 
	 * @param result
	 *            进行跳转中包含的所有信息
	 */
	@Override
	public void jsonWrite(Result result) throws IOException {
		this.httpContext.getResponse().setContentType("text/json");
		// 下面应该处理数据，暂时不开发
	}

	/**
	 * 普通的使用PrintWriter将数据回写，但目前不太确定该方法是否应存在
	 * 
	 * @param result
	 *            进行跳转中包含的所有信息
	 */
	@Override
	public void generalWrite(Result result) throws IOException {
		// 处理数据，暂时不开发
	}

	/**
	 * 当这个action执行完毕的时候应将对应的HttpContext移除，避免内存溢出
	 */
	@Override
	public void destoryHttpContext() {
		if (this.actionInc != null)
			ActionContext.remove(this.actionInc);
	}

	/**
	 * 将action中字段的值存储到request里面去
	 * 
	 * @param result
	 *            Result的实例
	 */
	@Override
	public void setAttributes(Result result) {
		if (result.getAttributes().size() > 1) {// 人为定义需要存放到request里面的字段
			for (String name : result.getAttributes()) {
				httpContext.getRequest().setAttribute(name, getValue(name));
			}
		} else {
			for (String name : fieldNames) {
				httpContext.getRequest().setAttribute(name, getValue(name));
			}
		}
	}

	/**
	 * 获得request中所有的参数和值并存放到list中去
	 */
	@Override
	public void getParameters() {
		HttpServletRequest request = httpContext.getRequest();
		Enumeration<String> names = request.getParameterNames();
		String name, value;
		while (names.hasMoreElements()) {
			name = names.nextElement();
			value = request.getParameter(name);
			parameters.put(name, value);
		}
	}

	/**
	 * 获得action下面所有字段
	 * 
	 * @param action
	 *            对应请求的action的po.Action的实例
	 */
	@Override
	public void getFields(Action action) {
		Class<?> clazz = actionInc.getClass();
		if (action.getParams().size() > 0) {// 如果人为设定了action需要接收的parameters
			for (String paramName : action.getParams()) {
				try {
					Field _field = clazz.getDeclaredField(paramName);
					fieldNames.add(_field.getName());
				} catch (SecurityException e) {
					logger.error(e.getMessage());
				} catch (NoSuchFieldException e) {
					logger.warn(Constants.dateFormat.format(new Date())
							+ " Warning:There is no field \"" + paramName
							+ "\" defined in action.");
				}
			}
		} else {// 没有人为定义需要接收的parameters，则默认加载所有的字段
			Field[] fields = clazz.getDeclaredFields();
			if (fields != null) {
				for (Field field : fields) {
					fieldNames.add(field.getName());
				}
			}
		}
	}

	/**
	 * 设定action中所有字段的值
	 * @time 2012-10-20 修复对象存储BUG
	 */
	@Override
	public void setFields() {
		try {
			for (String name : parameters.keySet()) {
				if (name.contains(".")) {// 如果用户提交的表单中参数名称包含.，则说明用户提交的数据可能是bean的信息
					int split = name.indexOf(".");
					String beanName = name.substring(0, split);
					if (fieldNames.contains(beanName)) {
						PropertyDescriptor pd = new PropertyDescriptor(
								beanName, actionInc.getClass());
						Class<?> beanClass = pd.getPropertyType();
						Object bean = pd.getReadMethod().invoke(actionInc);
						if (bean == null) {//只有当对象为NULL时，才创建并赋值
							bean = beanClass.newInstance();
							pd.getWriteMethod().invoke(actionInc, bean);
						}
						String fieldName = name.substring(split + 1);
						PropertyDescriptor fieldPd = new PropertyDescriptor(
								fieldName, beanClass);
						fieldPd.getWriteMethod().invoke(
								bean,
								TypeConvertUtil.convert(parameters.get(name),
										fieldPd.getPropertyType()));
					}
				} else {
					if (fieldNames.contains(name)) {
						this.setValue(name, parameters.get(name));
					}
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
	}

	/**
	 * 将值存放到对应的字段中去
	 * 
	 * @param name
	 *            字段的名称
	 * @param value
	 *            字段的值
	 * 
	 */
	private void setValue(String name, String value) {
		try {
			PropertyDescriptor pd = new PropertyDescriptor(name,
					actionInc.getClass());
			Class<?> type = pd.getPropertyType();
			Method mth = pd.getWriteMethod();
			Object val = TypeConvertUtil.convert(value, type);// 将string转换为对应的类型
			mth.invoke(actionInc, val);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
	}

	/**
	 * 通过名称获得action里的字段值
	 * 
	 * @param <T>
	 *            name 字段的名称
	 * @param name
	 *            字段名称
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private <T> T getValue(String name) {
		try {
			PropertyDescriptor pd = new PropertyDescriptor(name,
					actionInc.getClass());
			Method mth = pd.getReadMethod();
			return (T) mth.invoke(actionInc);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 重构重定向时的路径
	 * 
	 * @param result
	 *            Result的实例
	 */
	@Override
	public String rebuildLocation(Result result) {
		StringBuilder params = new StringBuilder("");
		List<Property> properties = result.getProperties();
		if (properties != null) {
			for (Property property : properties) {
				if (!property.getName().equals("location")) {// 除了路径外的其它信息应拼接在路径后面
					String content = property.getContent().trim();
					if (content.indexOf("${") == 0
							&& content.lastIndexOf("}") == content.length() - 1) {// 如果为EL表达式，则需要去解析它的值
						params.append("&")
								.append(property.getName())
								.append("=")
								.append(getValue(content.substring(2,
										content.length() - 1)));
					} else {// 普通的则直接当字符串拼接在路径后面
						params.append("&").append(property.getName())
								.append("=").append(content);
					}
				}
			}
		}
		if (params.length() > 1) {// 如果存在params，就需要将跳转路径进行重构
			return result.getLocation() + "?" + params.substring(1).toString();
		}
		return result.getLocation();
	}

}
